#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include "musiclib.h"
#include "fat32.h"
#include "debug.h"










MusicLib_t mLib;

// record sizes in bytes
#define ARTIST_REC_SIZE		38
#define ALBUM_REC_SIZE		34
#define SONG_REC_SIZE		41
#define LIB_HEADER_SIZE		18
#define LIB_REV_SIZE		12

// Array containing Main Menu strings
char * MainMenu[] = {"Artists","Albums","Songs", "Currently Playing"};

/****************************************************************************
* Purpose:	Initalizes the menu system.
* Input:	No Input
* Output:   Returns 1 on success, 0 on failure
****************************************************************************/
uint8_t initMusicLib()
{
	uint32_t filesize;

	memset(mLib.buf,0,512);
	mLib.playback_type = 0;
	mLib.libHeader.header[0] = '0';
	mLib.fp = NULL;
	mLib.cursorPos[0] = 0;
	mLib.AlbumOffset = 0;
	mLib.SongOffset = 0;
	mLib.currentAlbum.songs = 0;
	mLib.currentArtist.songs = 0;
	mLib.currentSong.filename[0] = '\0';

	// Attempt to open music library file
	mLib.fp = fat_fopen((uint8_t*)"MUSICLIBDAT");

	// If the file doesn't exist or we fail to open return error
	if (!mLib.fp)
	{
		printf("Couldn't Open MUSICLIBDAT\r\n");
		return 0;
	}

	// Buffer in the header
	fat_fread(mLib.buf, LIB_HEADER_SIZE, mLib.fp);

	// Copy the contents of the library head into
	memcpy(mLib.libHeader.header, mLib.buf, LIB_REV_SIZE);
	convertbuf2int(mLib.buf + LIB_REV_SIZE,		2, &(mLib.libHeader.artists) );
	convertbuf2int(mLib.buf + LIB_REV_SIZE + 2,	2, &(mLib.libHeader.albums) );
	convertbuf2int(mLib.buf + LIB_REV_SIZE + 4,	2, &(mLib.libHeader.songs) );

	//Verify this is correct file via header string
	if (memcmp(mLib.libHeader.header,"MUSICLIBv0.1",LIB_REV_SIZE) != 0)
	{
		printf("Bad musiclib file header\r\n");
		fat_fclose(mLib.fp);
		return 0;
	}

	// Ensure that there is sufficient amount of data
	if ( (mLib.libHeader.artists == 0) || (mLib.libHeader.albums == 0) || (mLib.libHeader.songs == 0) )
	{
		printf("Invalid Number of Artists, Albums, or Songs\r\n");
		fat_fclose(mLib.fp);
		return 0;
	}

	// Compute file size from internal data and verify against actual file size
	filesize = LIB_HEADER_SIZE + (mLib.libHeader.artists * ARTIST_REC_SIZE) + (mLib.libHeader.albums * ALBUM_REC_SIZE) + (mLib.libHeader.songs * SONG_REC_SIZE);

	if (filesize != mLib.fp->size)
	{
		printf("Invalid musiclib filesize: %lu, %lu\r\n", filesize, mLib.fp->size);
		fat_fclose(mLib.fp);
		return 0;
	}

	// Pre-compute album and song record offsets
	mLib.AlbumOffset = mLib.libHeader.artists * (ARTIST_REC_SIZE) + (LIB_HEADER_SIZE);
	mLib.SongOffset = mLib.AlbumOffset + (ALBUM_REC_SIZE)*mLib.libHeader.albums;

	// As a last form of verification check the actual number of songs
	// In root directory against num of songs spec'd by lib file
	if (mLib.libHeader.songs != fat_num_of_mp3_file() )
	{
		printf("Non-match Number of songs in datafile vs sdcard\r\n");
		fat_fclose(mLib.fp);
		return 0;
	}

	// Setup the menu position
	mLib.menu_depth = MAIN_MENU;

	// Setup the display
	updateDisplay();

	return 1;
}


/****************************************************************************
* Purpose:	Resets the music lib to inital state. Called when sd card is
*			removed
* Input:	No Input
* Output:   No Output
****************************************************************************/
void	disableMusicLib(void)
{
	// Release the file
	fat_fclose(mLib.fp);

	// Clear library data
	//memset(&mLib,0,sizeof(struct MusicLib_s));
	memset(mLib.buf,0,512);
	mLib.playback_type = 0;
	mLib.libHeader.header[0] = '0';
	mLib.fp = NULL;
	mLib.cursorPos[0] = 0;
	mLib.AlbumOffset = 0;
	mLib.SongOffset = 0;
	mLib.currentAlbum.songs = 0;
	mLib.currentArtist.songs = 0;
	mLib.currentSong.filename[0] = 0;
	mLib.current.songrec.filename[0] = 0;
	mLib.current.songrec.songname[0] = 0;
	mLib.current.count = 0;

	printf("Menu System Disabled\r\n");

}
/****************************************************************************
* Purpose:	Passes a input button to the menu system for interpretation
* Input:	Library Naviagtion command (LIB_UP, LIB_BACK, etc)
* Output:   Returns action that state machine should perform if any
****************************************************************************/
ButtonAction_t navigate(MusicLibCommand_t command)
{
	switch (command)
	{
		case LIB_SELECT:
			return doLibSelect();
			break;

		case LIB_BACK:
			doLibBack();
			return NO_ACTION;
			break;

		case LIB_UP:
			return doLibUp();
			break;

		case LIB_DOWN:
			return doLibDown();
			break;

		default:
			break;
	}
	return NO_ACTION;
}

/****************************************************************************
* Purpose:	Performs a LIB_SELECT action
* Input:	None
* Output:   Returns action that system needs to perform
****************************************************************************/
ButtonAction_t doLibSelect()
{
	ButtonAction_t ret = NO_ACTION;

	switch(mLib.menu_depth)
	{
		// If at main menu
		case MAIN_MENU:
			// Act accordingly to our cursor position
			if (mLib.cursorPos[0] == 0)
			{
				mLib.menu_depth = ARTIST_SORT;
				mLib.cursorPos[1] = 0;
				//Preload artist record
				getArtistRecord( &(mLib.currentArtist) );
			}
			else if (mLib.cursorPos[0] == 1)
			{
				mLib.menu_depth = ALBUM_SORT;
				mLib.cursorPos[1] = 0;
				// Preload album rec
				getAlbumRecord( &(mLib.currentAlbum) );
			}
			else if (mLib.cursorPos[0] == 2)
			{
				mLib.menu_depth = SONG_SORT;
				mLib.cursorPos[1] = 0;
				// Preload song rec
				getSongRecord( &(mLib.currentSong) );
			}

			else if (mLib.cursorPos[0] == 3)
			{
				mLib.menu_depth = CURRENT_SONG;
				// Clear the cursor array
				memset(mLib.cursorPos,0,sizeof(uint16_t)*4);
			}
			else // Should never get here, but just in case
			{
				mLib.menu_depth = MAIN_MENU;
				mLib.cursorPos[0] = 0;
			}

			ret = NO_ACTION;
			break;

		case ARTIST_SORT:
			// Move into artist album sort from current pos
			mLib.menu_depth = ARTIST_ALBUM_SORT;
			mLib.cursorPos[2] = 0;
			ret = NO_ACTION;
			break;

		case ARTIST_ALBUM_SORT:
			// Move into artist album song sort from current pos
			mLib.cursorPos[3] = 0;
			mLib.menu_depth = ARTIST_ALBUM_SONG_SORT;
			ret = NO_ACTION;
			break;

		case ALBUM_SORT:
			// Move into album song sort from current pos
			mLib.cursorPos[2] = 0;
			mLib.menu_depth = ALBUM_SONG_SORT;
			ret = NO_ACTION;
			break;

		case ARTIST_ALBUM_SONG_SORT:
		case ALBUM_SONG_SORT:
		case SONG_SORT:
			// Setup the playback type based on the current menu depth
			mLib.playback_type = mLib.menu_depth;

			// indicate to state machine that song playback has begun
			if ((mLib.menu_depth == ARTIST_ALBUM_SONG_SORT) || (mLib.menu_depth == ALBUM_SONG_SORT))
			{
				// Load mLib.current with appropriate data
				mLib.current.count = mLib.currentAlbum.songs;

				if (mLib.menu_depth == ARTIST_ALBUM_SONG_SORT)
				{
					// Get both artist and album records
					getArtistRecord(&mLib.current.artistrec);
					getAlbumRecord(&mLib.current.albumrec);
					mLib.current.num = mLib.cursorPos[3];
				}
				else
				{
					// Get only the ablum record
					getAlbumRecord(&mLib.current.albumrec);
					mLib.current.num = mLib.cursorPos[2];
				}

			}
			else // If in song sort, the song num is the cursor position
				mLib.current.num = mLib.cursorPos[1];

			// Load the song record
			getSongRecord( &(mLib.current.songrec) );

			// Move to currently playing menu display
			mLib.menu_depth = CURRENT_SONG;

			// Clear the cursors
			memset(mLib.cursorPos,0,sizeof(uint16_t)*4);

			// Indicate a new selection has been made
			ret = NEW_SONG_SELECTED;

			break;

		case CURRENT_SONG:
			// If currently playing, then pause
			ret = PAUSE_PLAY;
			break;
	}

	// Refresh the display
	updateDisplay();

	return ret;
}

/****************************************************************************
* Purpose:	Performs a LIB_BACK action
* Input:	None
* Output:   None
****************************************************************************/
void doLibBack()
{
	switch(mLib.menu_depth)
	{
		case MAIN_MENU:
			// Do nothing
			break;

		case ARTIST_SORT:
		case ALBUM_SORT:
		case SONG_SORT:
			// Move back to main menu
			mLib.menu_depth = MAIN_MENU;
			mLib.cursorPos[1] = 0;
			break;

		case ARTIST_ALBUM_SORT:
			mLib.menu_depth = ARTIST_SORT;
			mLib.cursorPos[2] = 0;
			break;

		case ARTIST_ALBUM_SONG_SORT:
			mLib.menu_depth = ARTIST_ALBUM_SORT;
			mLib.cursorPos[3] = 0;
			break;

		case ALBUM_SONG_SORT:
			mLib.menu_depth = ALBUM_SORT;
			mLib.cursorPos[2] = 0;
			break;

		case CURRENT_SONG:
			mLib.menu_depth = MAIN_MENU;
			mLib.cursorPos[1] = 0;
			break;
	}

	updateDisplay();

	return;
}

/****************************************************************************
* Purpose:	Performs a LIB_UP action
* Input:	None
* Output:   Returns action that system needs to perform
****************************************************************************/
ButtonAction_t doLibUp()
{
	uint8_t level = 0;

	// Set value of level, so we can determine which cursor pos we should modify
	switch(mLib.menu_depth)
	{
		case MAIN_MENU:
			level = 0;
			break;

		case ARTIST_SORT:
		case ALBUM_SORT:
		case SONG_SORT:

			level = 1;
			break;

		case ARTIST_ALBUM_SORT:
		case ALBUM_SONG_SORT:

			level = 2;
			break;

		case ARTIST_ALBUM_SONG_SORT:

			level = 3;
			break;

		case CURRENT_SONG:
			// If in currently playing screen skip songs
			return SONG_NEXT;

	}

	// Decrease cursor iff we are not already at minimum
	if (mLib.cursorPos[level] > 0)
	{
		mLib.cursorPos[level]--;

		//Refresh the display
		updateDisplay();
	}
	return NO_ACTION;
}

/****************************************************************************
* Purpose:	Performs a LIB_DOWN action
* Input:	None
* Output:   Returns action that system needs to perform
****************************************************************************/
ButtonAction_t doLibDown()
{
	switch(mLib.menu_depth)
	{
		case MAIN_MENU:
			// Only show currently playing iff have actually loaded a song for the first
			// time
			if (mLib.cursorPos[0] == 2 && !(isprint(mLib.current.songrec.songname[0])) )
				return NO_ACTION;

			// Otherwise display currently playing menu selection
			else if ( mLib.cursorPos[0] < 3)
				mLib.cursorPos[0]++;

			else // If at the bottom of menu don't do anything
				return NO_ACTION;

			break;

		case ARTIST_SORT:
			// Figure out if we are at the bottom of artists menu
			if(mLib.cursorPos[1] < (mLib.libHeader.artists - 1))
				mLib.cursorPos[1]++;
			else
				return NO_ACTION;
			break;

		case ALBUM_SORT:
			// Figure out if we are at the bottom of album menu
			if(mLib.cursorPos[1] < (mLib.libHeader.albums - 1))
				mLib.cursorPos[1]++;
			else
				return NO_ACTION;
			break;

		case SONG_SORT:
			// Figure out if we are at the bottom of song menu
			if(mLib.cursorPos[1] < (mLib.libHeader.songs - 1))
				mLib.cursorPos[1]++;
			else
				return NO_ACTION;
			break;

		case ARTIST_ALBUM_SORT:
		// Figure out if we are at the bottom of artist/album menu
			if(mLib.cursorPos[2] < (mLib.currentArtist.albums - 1))
				mLib.cursorPos[2]++;
			else
				return NO_ACTION;
			break;

		case ALBUM_SONG_SORT:
			// Figure out if we are at the bottom of album/song menu
			if(mLib.cursorPos[2] < (mLib.currentAlbum.songs - 1))
				mLib.cursorPos[2]++;
			else
				return NO_ACTION;
				break;
		case ARTIST_ALBUM_SONG_SORT:
			// Figure out if we are at the bottom of artist/album/song menu
			if(mLib.cursorPos[3] < (mLib.currentAlbum.songs - 1))
				mLib.cursorPos[3]++;
			else
				return NO_ACTION;
			break;

		case CURRENT_SONG:
			return SONG_PREV;

			break;
	}

	// Refresh Display
	updateDisplay();

	return NO_ACTION;
}

/****************************************************************************
* Purpose:	Gets the current song selection for playback
* Input:	None
* Output:   Returns a valid fileptr on success, null on failure
****************************************************************************/
FAT_FILE_t * getSelection()
{
	// Attempt to open current selection
	FAT_FILE_t *fp;

	fp = fat_fopen(mLib.current.songrec.filename);

	return fp;
}

/****************************************************************************
* Purpose:	Gets the next song selection for playback
* Input:	None
* Output:   Returns a valid fileptr on success, null on failure
****************************************************************************/
FAT_FILE_t * getNextSong()
{
	FAT_FILE_t * fp;

	// Act accordingly to playback type for how to find next song
	if (mLib.playback_type == ARTIST_ALBUM_SONG_SORT)
	{
		if (mLib.current.num < (mLib.current.count - 1))
		{
			mLib.current.num++;

			getPlaybackSongRecord( &(mLib.current.songrec) );

			fp = fat_fopen(mLib.current.songrec.filename);

			return fp;
		}
		else
			return NULL;
	}
	else if (mLib.playback_type == ALBUM_SONG_SORT)
	{
		if (mLib.current.num < (mLib.current.count - 1))
		{
			mLib.current.num++;
			getPlaybackSongRecord( &(mLib.current.songrec) );

			fp = fat_fopen(mLib.current.songrec.filename);

			return fp;
		}
		else
			return NULL;
	}
	else if (mLib.playback_type == SONG_SORT)
	{
		if (mLib.current.num < (mLib.libHeader.songs - 1))
		{
			mLib.current.num++;

			getPlaybackSongRecord(&(mLib.current.songrec));

			fp = fat_fopen(mLib.current.songrec.filename);

			return fp;
		}
		else
			return NULL;
	}
	return NULL;

}

/****************************************************************************
* Purpose:	Gets the prev song selection for playback
* Input:	None
* Output:   Returns a valid fileptr on success, null on failure
****************************************************************************/
FAT_FILE_t * getPrevSong()
{
	FAT_FILE_t * fp;

	if (mLib.playback_type == ARTIST_ALBUM_SONG_SORT)
	{
		if (mLib.current.num > 0)
		{
			mLib.current.num--;
		}

		getPlaybackSongRecord( &(mLib.current.songrec) );

		fp = fat_fopen(mLib.current.songrec.filename);

		return fp;
	}

	else if (mLib.playback_type == ALBUM_SONG_SORT)
	{
		if (mLib.current.num > 0)
		{
			mLib.current.num--;
		}

		getPlaybackSongRecord( &(mLib.current.songrec) );

		fp = fat_fopen(mLib.current.songrec.filename);

		return fp;
	}
	else
	{
		if (mLib.current.num > 0)
		{
			mLib.current.num--;
		}

		getPlaybackSongRecord( &(mLib.current.songrec) );

		fp = fat_fopen(mLib.current.songrec.filename);

		return fp;

	}
	return NULL;
}

/****************************************************************************
* Purpose:	Loads current.songrec with valid record information
*			depeding on playback_method
* Input:	Pointer to song record
* Output:   None
****************************************************************************/
void getPlaybackSongRecord(SongRecord_t * rec)
{
	uint16_t offset;

	if (mLib.playback_type == ARTIST_ALBUM_SONG_SORT)
	{
		offset = (mLib.current.albumrec.songindex + mLib.current.num) * SONG_REC_SIZE;
	}
	else if (mLib.playback_type == ALBUM_SONG_SORT)
	{
		offset = (mLib.current.albumrec.songindex + mLib.current.num) * SONG_REC_SIZE;
	}
	else if (mLib.playback_type == SONG_SORT)
	{
		offset = mLib.current.num * SONG_REC_SIZE;
	}

	else
		return;

	fat_fseek(mLib.fp,mLib.fp->size - (offset + mLib.SongOffset),1);

	fat_fread(mLib.buf, SONG_REC_SIZE, mLib.fp);

	memcpy(rec->filename,mLib.buf, 11);

	memcpy(rec->songname,mLib.buf+11 , 30);

}

/****************************************************************************
* Purpose:	Loads a song record into record pointer "rec" according to
*			menu state.
* Input:	Pointer to Song Record
* Output:   None
****************************************************************************/
void getSongRecord(SongRecord_t * rec)
{
	uint16_t offset;

	if (mLib.menu_depth == ARTIST_ALBUM_SONG_SORT)
	{
		offset = mLib.SongOffset + ( (mLib.currentAlbum.songindex + mLib.cursorPos[3]) * SONG_REC_SIZE );
	}
	else if (mLib.menu_depth == ALBUM_SONG_SORT)
	{
		offset = mLib.SongOffset + ( (mLib.currentAlbum.songindex + mLib.cursorPos[2]) * SONG_REC_SIZE );
	}
	else if (mLib.menu_depth == SONG_SORT)
	{
		offset = mLib.SongOffset + (mLib.cursorPos[1] * SONG_REC_SIZE);
	}

	else
		return;

	fat_fseek(mLib.fp, mLib.fp->size - offset, 1);

	fat_fread(mLib.buf, SONG_REC_SIZE, mLib.fp);

	memcpy(rec->filename, mLib.buf, 11);
	memcpy(rec->songname, mLib.buf+11, 30);
}

/****************************************************************************
* Purpose:	Loads a album record into record pointer "rec" according to
*			menu state.
* Input:	Pointer to Album Record
* Output:   None
****************************************************************************/
void getAlbumRecord(AlbumRecord_t * rec)
{
	uint32_t offset;

	if (mLib.menu_depth == ARTIST_ALBUM_SORT || mLib.menu_depth == ARTIST_ALBUM_SONG_SORT)
	{
		offset = mLib.AlbumOffset + ( (mLib.cursorPos[2] + mLib.currentArtist.albumindex) * ALBUM_REC_SIZE );
	}
	else if (mLib.menu_depth == ALBUM_SORT || mLib.menu_depth == ALBUM_SONG_SORT)
	{
		offset = mLib.AlbumOffset + ( ALBUM_REC_SIZE * mLib.cursorPos[1] );
	}
	else
		return;

	fat_fseek(mLib.fp,mLib.fp->size - offset, 1);

	fat_fread(mLib.buf, ALBUM_REC_SIZE, mLib.fp);

	memcpy(rec->albumname,mLib.buf,30);

	convertbuf2int(mLib.buf+30, 2, &(rec->songs));
	convertbuf2int(mLib.buf+32, 2, &(rec->songindex));

}

/****************************************************************************
* Purpose:	Loads a artist record into record pointer "rec" according to
*			menu state.
* Input:	Pointer to Artist Record
* Output:   None
****************************************************************************/
void getArtistRecord(ArtistRecord_t * rec)
{
	uint32_t offset;

	if (mLib.menu_depth == ARTIST_SORT || mLib.menu_depth == ARTIST_ALBUM_SORT || mLib.menu_depth == ARTIST_ALBUM_SONG_SORT)
	{
		offset = (mLib.cursorPos[1] * ARTIST_REC_SIZE) + (LIB_HEADER_SIZE);
	}
	else
		return;

	fat_fseek(mLib.fp, (mLib.fp->size - offset), 1);

	fat_fread(mLib.buf, ARTIST_REC_SIZE, mLib.fp);

	memcpy(rec->aristname, mLib.buf, 30);

	convertbuf2int(mLib.buf+30, 2, &(rec->albums));
	convertbuf2int(mLib.buf+32, 2, &(rec->albumindex));
	convertbuf2int(mLib.buf+34, 2, &(rec->songs));
	convertbuf2int(mLib.buf+36, 2, &(rec->songindex));
}

/****************************************************************************
* Purpose:	Updates the display
* Input:	None
* Output:   None
****************************************************************************/
void updateDisplay()
{
	displayClear();

	switch(mLib.menu_depth)
	{
		case MAIN_MENU:
			displayWrite( (uint8_t*)MainMenu[mLib.cursorPos[0]] );
			break;

		case ARTIST_SORT:
			getArtistRecord( &(mLib.currentArtist) );
			displayWrite( (uint8_t*)mLib.currentArtist.aristname );
			break;

		case ALBUM_SORT:
		case ARTIST_ALBUM_SORT:
			getAlbumRecord( &(mLib.currentAlbum) );
			displayWrite( (uint8_t*)mLib.currentAlbum.albumname );

			break;

		case SONG_SORT:
		case ALBUM_SONG_SORT:
		case ARTIST_ALBUM_SONG_SORT:
			getSongRecord( &(mLib.currentSong)) ;
			displayWrite( (uint8_t*)mLib.currentSong.songname );
			break;

		case CURRENT_SONG:
			displayWrite( (uint8_t*)mLib.current.songrec.songname );
			break;
	}
}

/****************************************************************************
* Purpose:	Clears the display
* Input:	None
* Output:   None
****************************************************************************/
void displayClear()
{

}

/****************************************************************************
* Purpose:	Writes a string to the display
* Input:	Pointer to unsigned string
* Output:   None
****************************************************************************/
void displayWrite(uint8_t * str)
{
	printf("%s",(char*)str);
	printf("\r\n");
}